﻿using System;
using System.Diagnostics;
using System.Reflection.Emit;

namespace Aeon.Emulator.Decoding.Emitters
{
    /// <summary>
    /// Emitter for decoding an operand that may be a register or a memory address.
    /// </summary>
    internal abstract class LoadRegRmw : Emitter
    {
        #region Constructors
        /// <summary>
        /// Initializes a new instance of the LoadRegRmw class.
        /// </summary>
        /// <param name="state">Common emitter state information.</param>
        /// <param name="valueSize">Size of the value to load in bytes.</param>
        /// <param name="memoryOnly">Indicates whether the operand will only be a memory address.</param>
        /// <param name="floatingPoint">Indicates whether the value is a floating-point value in memory.</param>
        /// <param name="offsetOnly">Indicates whether only an effective address should be generated.</param>
        protected LoadRegRmw(EmitStateInfo state, int valueSize, bool memoryOnly, bool floatingPoint, bool offsetOnly)
            : base(state)
        {
            if(valueSize != 1 && valueSize != 2 && valueSize != 4 && valueSize != 6 && valueSize != 8 && valueSize != 10)
                throw new ArgumentException("Invalid size.", "valueSize");
            if(!memoryOnly && offsetOnly)
                throw new ArgumentException("Effective address invalid for registers.", "offsetOnly");

            this.ValueSize = valueSize;
            this.MemoryOnly = memoryOnly;
            this.FloatingPoint = floatingPoint;
            this.OffsetOnly = offsetOnly;
        }
        #endregion

        #region Public Properties
        /// <summary>
        /// Gets the size of the value to load in bytes.
        /// </summary>
        public int ValueSize { get; private set; }
        /// <summary>
        /// Gets a value indicating whether the operand will only be a memory address.
        /// </summary>
        public bool MemoryOnly { get; private set; }
        /// <summary>
        /// Gets a value indicating whether the operand is a floating-point value in memory.
        /// </summary>
        public bool FloatingPoint { get; private set; }
        /// <summary>
        /// Gets a value indicating whether only an effective address should be generated.
        /// </summary>
        public bool OffsetOnly { get; private set; }
        /// <summary>
        /// Gets the type of local variable needed to store the operand value as a method argument.
        /// </summary>
        public override Type MethodArgType
        {
            get
            {
                if(this.ReturnType == EmitReturnType.Value)
                    return this.TempType;
                else
                    return typeof(IntPtr);
            }
        }
        /// <summary>
        /// Gets a value indicating whether a temporary write-back variable is or may be required.
        /// </summary>
        public override bool? RequiresTemp
        {
            get
            {
                if(this.ReturnType == EmitReturnType.Value || this.OffsetOnly)
                    return false;
                else if(this.MemoryOnly)
                    return true;
                else
                    return null;
            }
        }
        /// <summary>
        /// Gets the type of local variable required to store temporary write-back values.
        /// </summary>
        public override Type TempType
        {
            get
            {
                if(!this.FloatingPoint)
                    return GetUnsignedIntType(this.ValueSize);
                else
                    return GetFloatType(this.ValueSize);
            }
        }
        /// <summary>
        /// Gets the local variable which contains the parameter is address boolean value if applicable.
        /// </summary>
        public override LocalBuilder ParamIsAddressLocal
        {
            get { return this.paramIsAddress; }
        }
        #endregion

        #region Public Static Methods
        /// <summary>
        /// Returns a new instance of the appropriate LoadRegRmw class.
        /// </summary>
        /// <param name="state">Common emitter state information.</param>
        /// <param name="valueSize">Size of the value to load in bytes.</param>
        /// <param name="memoryOnly">Indicates whether the operand will only be a memory address.</param>
        /// <returns>New RegRmw instance.</returns>
        public static LoadRegRmw Create(EmitStateInfo state, int valueSize, bool memoryOnly)
        {
            if(state.AddressMode == 16)
                return new LoadRegRmw16(state, valueSize, memoryOnly, false, false);
            else if(state.AddressMode == 32)
                return new LoadRegRmw32(state, valueSize, memoryOnly, false, false);
            else
                throw new ArgumentException("Invalid addressing mode.", "addressMode");
        }
        /// <summary>
        /// Returns a new instance of the appropriate LoadRegRmw class.
        /// </summary>
        /// <param name="state">Common emitter state information.</param>
        /// <param name="valueSize">Size of the value to load in bytes.</param>
        /// <returns>New RegRmw instance.</returns>
        public static LoadRegRmw CreateFloat(EmitStateInfo state, int valueSize)
        {
            if(state.AddressMode == 16)
                return new LoadRegRmw16(state, valueSize, true, true, false);
            else if(state.AddressMode == 32)
                return new LoadRegRmw32(state, valueSize, true, true, false);
            else
                throw new ArgumentException("Invalid addressing mode.", "addressMode");
        }
        /// <summary>
        /// Returns a new instance of the appropriate LoadRegRmw class.
        /// </summary>
        /// <param name="state">Common emitter state information.</param>
        /// <returns>New RegRmw instance.</returns>
        public static LoadRegRmw CreateEffectiveAddress(EmitStateInfo state)
        {
            if(state.AddressMode == 16)
                return new LoadRegRmw16(state, 2, true, false, true);
            else if(state.AddressMode == 32)
                return new LoadRegRmw32(state, 4, true, false, true);
            else
                throw new ArgumentException("Invalid addressing mode.", "addressMode");
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// Emits IL for the operation.
        /// </summary>
        public sealed override void EmitLoad()
        {
            bool byteVersion = this.ValueSize == 1;
            bool returnValue = this.ReturnType == EmitReturnType.Value;

            var doneLabel = il.DefineLabel();

            var rmLocal = il.DeclareLocal(typeof(int));
            var modLocal = il.DeclareLocal(typeof(int));

            //// R/M is the low 3 bits of the ModR/M byte.
            //int rm = *ip & 0x07;
            //// Mod is the high 2 bits of the ModR/M byte.
            //int mod = (*ip & 0xC0) >> 6;

            //il.LoadLocal(ipPointer);
            LoadIPPointer();
            il.Emit(OpCodes.Ldind_U1);
            il.Emit(OpCodes.Dup);

            il.LoadConstant(0x07);
            il.Emit(OpCodes.And);
            il.StoreLocal(rmLocal);

            il.LoadConstant(0xC0);
            il.Emit(OpCodes.And);
            il.LoadConstant(6);
            il.Emit(OpCodes.Shr_Un);
            il.StoreLocal(modLocal);

            // This stuff is only needed when the operand could be a register.
            if(!this.MemoryOnly)
            {
                Label memoryLabel = il.DefineLabel();

                // This will store a value that indicates whether the operand is a pointer to a register.
                if(!returnValue)
                {
                    this.paramIsAddress = il.DeclareLocal(typeof(bool));
                    il.LoadConstant(0);
                    il.StoreLocal(this.paramIsAddress);
                }

                // If modLocal != 3, go to memoryLabel.
                il.LoadLocal(modLocal);
                il.LoadConstant(3);
                il.Emit(OpCodes.Ceq);
                il.Emit(OpCodes.Brfalse_S, memoryLabel);

                // Handle register case.
                LoadProcessor();
                il.LoadLocal(rmLocal);
                if(!byteVersion)
                {
                    il.Emit(OpCodes.Call, Infos.Processor.GetRegisterWordPointer);
                    if(returnValue)
                        il.Emit(this.ValueSize == 4 ? OpCodes.Ldind_U4 : OpCodes.Ldind_U2);
                }
                else
                {
                    il.Emit(OpCodes.Call, Infos.Processor.GetRegisterBytePointer);
                    if(returnValue)
                        il.Emit(OpCodes.Ldind_U1);
                }

                // Advance past the ModRM byte.
                IncrementIPPointer(1);
                il.Emit(OpCodes.Br, doneLabel);

                il.MarkLabel(memoryLabel);
            }
            else
                EmitMod3Test(modLocal);

            if(returnValue && !this.OffsetOnly)
                LoadPhysicalMemory();

            if(!this.OffsetOnly)
            {
                // Handle memory case.  Address will be on the top of the stack after this call.
                LoadPhysicalAddress(rmLocal, modLocal);
            }
            else
            {
                // Handle memory case.  Offset will be on the top of the stack after this call.
                LoadAddressOffset(rmLocal, modLocal);
            }

            if(returnValue && !this.OffsetOnly)
            {
                if(!this.FloatingPoint)
                    CallGetMemoryInt(this.ValueSize);
                else
                    CallGetMemoryReal(this.ValueSize);
            }
            else if(!this.MemoryOnly)
            {
                il.LoadConstant(1);
                il.StoreLocal(paramIsAddress);
            }

            il.MarkLabel(doneLabel);
        }
        #endregion

        #region Protected Methods
        /// <summary>
        /// Loads the linear 32-bit address of a memory operand.
        /// </summary>
        /// <param name="rmLocal">Local variable containing the R/M code.</param>
        /// <param name="modLocal">Local variable containing the Mod code.</param>
        protected abstract void LoadPhysicalAddress(LocalBuilder rmLocal, LocalBuilder modLocal);
        /// <summary>
        /// Loads the 16 or 32-bit offset of a memory operand.
        /// </summary>
        /// <param name="rmLocal">Local variable containing the R/M code.</param>
        /// <param name="modLocal">Local variable containing the Mod code.</param>
        protected abstract void LoadAddressOffset(LocalBuilder rmLocal, LocalBuilder modLocal);
        #endregion

        #region Private Methods
        /// <summary>
        /// Emits additional IL for verifying that Mod is not 3 on memory-only operands.
        /// </summary>
        /// <param name="modLocal">Local variable containing the Mod code.</param>
        [Conditional("DEBUG")]
        private void EmitMod3Test(LocalBuilder modLocal)
        {
            var modNot3Label = il.DefineLabel();
            il.LoadLocal(modLocal);
            il.LoadConstant(3);
            il.Emit(OpCodes.Ceq);
            il.Emit(OpCodes.Brfalse_S, modNot3Label);

            il.Emit(OpCodes.Newobj, typeof(Mod3Exception).GetConstructor(Type.EmptyTypes));
            il.Emit(OpCodes.Call, Infos.RuntimeCalls.ThrowException);

            il.Emit(OpCodes.Ret);

            il.MarkLabel(modNot3Label);
        }
        #endregion

        #region Private Fields
        /// <summary>
        /// Local variable indicating whether the operand is a pointer to a register.
        /// </summary>
        private LocalBuilder paramIsAddress;
        #endregion
    }
}
